iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
AI Engineering

不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天系列 第 15

Day 15 - 12B 的 KV cache 為什麼比 117B 還貴?因為你只看了一本帳

  • 分享至 

  • xImage
  •  

昨天那張速查表有一格很怪。

Gemma 4 只有 12B,一條 8K 對話卻要吃 448 MiB;gpt-oss-120b 有 117B,反而只要 293 MiB。

小十倍的模型,KV 為什麼比較貴?

https://ithelp.ithome.com.tw/upload/images/20260826/20183550zztG5cP9Qv.png
圖 1:黃色是不再隨 context 繼續長大的那部分——Qwen 是固定 state,Gemma 與 gpt-oss 則是封頂的 sliding-window KV。

因為我們一直把 KV cache 當成一本帳。它至少有三本:

① 每序列總量 = 隨 context 長大的 cache
               + 已封頂的 cache
               + 每序列固定 state
② 每步碰多少 = 這一步真正讀進 attention/state 計算的歷史資料
③ HBM 常駐   = ① 裡面此刻非得留在顯存的那一部分

而每一招通常只動其中一本。看到「省 30 倍」的時候,另外兩本可能一毛都沒少。

https://ithelp.ithome.com.tw/upload/images/20260826/20183550iXMicDhUwX.png
圖 2:第三本帳不是新問題,8 月的 HiSparse 只是把它做成了一個很乾淨的例子。

先說清楚一件事:①裡有些東西嚴格說不是 KV cache,例如線性層的遞迴 state。我把它們算進同一本,是因為做容量估算時它們是同一種成本——每多一條 sequence 就多付一次。

第一本帳:一條對話要存多少

Qwen3.6-35B-A3B 很省,不是因為它只有 35B,而是因為 40 層裡只有 10 層真的需要一路存 KV,其餘 30 層換成了 Gated DeltaNet 線性注意力。

每 token KV = 2 × 10 層 × 2 head × 256 dim × 2 B = 20 KiB @FP16
Llama-3.3-70B 同一條算式 = 320 KiB              ← 十六分之一

8K 對話就是 160 MiB。但省掉的東西沒有消失:那 30 層改成每條 sequence 帶一筆 61.4 MiB 的固定 state,context 拉到 128K 它不會變大,KV 壓成 FP8 它也不跟著減半。

@FP16   KV 160 MiB + state 61.4 MiB = 221.4 MiB
@FP8    KV  80 MiB + state 61.4 MiB = 141.4 MiB   KV 砍半,總量只掉三成六

第一個規律:有些架構省掉的是「隨 context 長大的那部分」,代價搬到固定成本。

想自己查,config 欄位是 full_attention_interval: 4。這套配方沒有換代——8 月上架的 Qwen3.8-27B,影響這筆帳的 text_config 欄位原封不動沿用 Qwen3.6-27B,連參數量都同一個數。

固定 state 的 dtype 要看 runtime,不是看權重精度:Qwen 這幾顆的 config 寫 mamba_ssm_dtype: "float32",vLLM 的 --mamba-ssm-cache-dtype 預設 auto 會直接讀它。

Kimi K3 把同一招推到極致:93 層裡 69 層是 KDA 線性注意力,24 層是 Gated MLA,最後一層必為全域注意力。留下來那 24 層還壓成 512+64 維的 latent,每 token 27 KiB——2.8T 的模型,KV 單價還不到 27B 那顆 Qwen 的一半。代價在另一邊:權重檔實測 1.56 TB(=1.42 TiB),T1 到 T4 全數出局。

還是第一本帳,但 Gemma 4 換了個砍法

Gemma 4 12B 的 48 層裡有 40 層是 sliding window,只看最近 1,024 個 token;每 6 層才有 1 層 global。sliding 層的 cache 因此有天花板,@FP16 封在 320 MiB,之後再長也不會變大。

代價就是圖 1 那格:@8K 時這 320 MiB 佔掉每序列成本的七成。它省的不是 KV 的單價,是 KV 的成長速度,所以兩條線一定會交叉:

Gemma 4 12B     320 MiB + 16 KiB × context
gpt-oss-120b    4.5 MiB + 36 KiB × context
                ────────────────────────────
交叉點           context ≈ 16,150 token

@8K     448  vs  293 MiB      12B 輸
@128K   2.31 vs 4.50 GiB      12B 省快一半

你把 context 設多長,決定了這格是優點還是缺點。

global 那 8 層還有一刀,值得自己算一次。官方報告寫 global 層「values = keys」,配上 p-RoPE 的 p = 0.25,「effectively reducing the global KV cache by 37.5%」。global head_dim 是 512,只有 0.25 × 512 = 128 維過旋轉,其餘 384 維 K 與 V 同源、可以只存一份:

不共用   K 512 + V 512               = 1,024
共用後   V 512 + K 的旋轉切片 128     =   640
                              1 − 640 ÷ 1,024 = 37.5%

Day 09 我寫過「K 過了旋轉、V 沒有,兩份仍要各存」——前半對,後半太保守:真正不能共用的只有那 128 維。不過本系列的表照舊按 16 KiB/token 算,因為 HF transformers 的參考實作把 K、V 兩份完整張量都塞進 cache。37.5% 是架構允許的,不是你在本機量得到的。

順帶記兩件事:這一招在 E2B 與 E4B 上是關的,而 256K context 也只有 12B、26B-A4B、31B 才有。

第二本帳:每生成一步要碰多少

第二本帳砍的是「每個 token 要看多少 KV」。典型做法不動 cache 的大小,只挑著讀。

GLM-5.2(753B)用 DSA:一個 indexer 對每個 query 只挑 top-2048 條進 attention,5.2 再讓每 4 層共用一個 indexer(model card 叫 IndexShare),1M context 下 per-token FLOPs 降 2.9 倍。

它的 KV 本體仍然是 MLA latent,78 層 ×(512+64)維,每 token 87.75 KiB @FP16,還不到 Llama-3.3-70B 的三分之一。但那是 MLA 的功勞,不是 DSA 的——DSA 省的是「讀多少」,一條都沒少存。

DeepSeek-V4-Flash-0731(官方標稱 284B/13B 啟用)則是兩本一起砍:CSA 先把 KV 按 4 與 128 兩種比例壓成塊,再對壓縮塊選讀 top-512。官方報告是 1M context 下相對 V3.2 只要 10% FLOPs、7% KV cache——它能同時給出這兩個數字,是因為壓完之後原始的 KV 是真的丟掉的。

補一個容易混的邊界:在 bs=1 的 decode,第二本帳多半先表現成 memory traffic,query 只有一個、attention 受頻寬限制。真正會隨序列長度冒出 O(L²) 的是 full-attention prefill。

第三本帳:這些 KV 一定要住在 GPU 嗎

前面兩本今天舉的例子大多是架構先決定的(Quest 那種 training-free selector 是例外);第三本則更像 serving system 的問題。8 月初的 HiSparse(arXiv:2608.07009)把這一刀做得很乾淨,它開頭第一句就戳破了第二本帳的尷尬:attention 便宜了幾個數量級,記憶體帳單卻一個 byte 都沒少

因為這一步沒被選中的 token,下一步可能突然被選中,所以整條 context 的 KV 都得留在 HBM 裡待命。HiSparse 一筆都不刪,只叫它搬家:

https://ithelp.ithome.com.tw/upload/images/20260826/20183550FYKn11ojWg.png
圖 3:右邊那塊 hot cache 的大小 B 是你設的,跟對話多長無關。

論文按 footprint 上界推算:GLM-5.1 一條 128K 的請求,attention KV 的 GPU 常駐從 13.09 GB 掉到約 0.4 GB,約 30 倍。那 13 GB 沒有蒸發,它搬去 host RAM,miss 的時候走 PCIe/NVLink-C2C。

「搬走的只有 KV 本體」這句要記牢:selection state、page table 與 LRU metadata 都還留在 GPU。前兩者仍會隨 context 長,LRU 則是跟 hot cache 的 B 走。但這些東西小得多——論文估 indexer key 加 page-table entry 至多每 token 幾百 bytes,對面是約 100 KB 的 KV records。

它最漂亮的地方是沒有再近似一次:selected positions、attention score、output 全都不變,改的只有 KV 住哪。但這個 exact 是相對於模型自己的 sparse 規則——拿 Quest 那種把 dense 模型硬改成選讀的做法,近似在 HiSparse 之前就發生了。

論文那個 4.7×,收益來源它自己講死了:HiSparse 不會讓單一 decode step 變快,只是讓同一塊 HBM 裝得下更大的 decode batch。它換來的是 aggregate throughput,不是你一個人的 tok/s。

那你開得起來嗎?已經 merge 進 SGLang upstream(--enable-hisparse),但官方文件目前只涵蓋 DSA 架構與 DeepSeek V4,而且只在 PD 分離部署的 decode instance 上啟用——單卡跑 llama.cpp 或 Ollama 的今天用不到。

三本帳是先天的,runtime 還能後天改造

架構決定體質,serving 還有一層:KV 換 FP8 讓每一筆變小,直接砍①,通常也連帶縮③;PagedAttention 不改單價,減的是碎片與預留浪費;prefix cache 讓相同 prefix 的 KV block 直接重用,省的是重複 prefill,不會讓新 token 的 decode TPOT 變快(vLLM 官方文件自己寫的)。

地端這邊先對一次帳:llama.cpp 的 KV 量化是 -ctk-ctv 那組 ggml 型別(預設 f16)、不是 FP8,量化 V 一定要開 -fa;prompt caching 有而且預設開;至於 PagedAttention,它走的是自己的 unified KV/slot 路線,帳法不能拿 vLLM 那套直接套。

一張總表:每一招動哪一本帳

招式 ① 每序列總量 ② 每步碰多少 ③ HBM 常駐 代價搬到哪
GQA/MQA 較少獨立 K/V head,靠訓練補回來
MLA latent 壓縮 ↓↓ ↓↓ 多一組低秩投影與更複雜的 kernel
線性層(GDN/KDA) ↓↓↓ ↓↓↓ ↓↓↓ 每序列 61–147 MiB 固定 state
sliding window 封頂 ↓↓ 封頂 那些層看不到遠處
DSA/NSA 選讀 KV 主體不變 ↓↓↓ 不變 indexer 自己要算力,也要 cache
CSA(壓塊+選讀) ↓↓ ↓↓↓ ↓↓ 壓縮塊本身是近似
HiSparse 分層存放 邏輯 KV 不變 邏輯不變 KV ↓↓↓ host RAM + miss 的 IO

只有最後一列的 ③ 會跟 ① 分家——同一批 KV 一筆沒少,只是不再全部住顯存。那正是第三本帳存在的理由。

查證日 2026-08-26,KV 一律按 FP16,逐項條件見各圖的來源行。三個最容易被拿去亂引的數字:30× 是 HBM footprint、4.7× 是特定 workload 的 aggregate throughput、2.9× 是 FLOPs,三者不是同一件事。HiSparse 的快取命中率(§4.3)與 TPOT 代價(§4.6)論文都有給,這裡不轉述。V4-Flash 的 284B 是官方對主模型架構的標稱;HF safetensors 逐 tensor 加總則是 preview 290.9B、-0731 304.2B,兩份實測相差的 13.3B 才是隨附的 DSpark module(Day 06 那格用的是 304B)。兩種口徑不要直接相減——標稱對不上逐 tensor 加總是 Day 04 講過的老現象。

還有兩刀完全不碰 KV:MoE 動的是權重帳,MTP 動的是 forward 次數。今天先不開這兩本。

今天的實驗需要什麼

不用 GPU。下次遇到陌生模型,先照這張圖看五格:

https://ithelp.ithome.com.tw/upload/images/20260826/20183550sTHIjucXhM.png
圖 4:第 5 列最容易漏——它不隨 context 縮,所以短對話反而最痛。

然後兩行 curl,不下載權重也能親眼看到今天講的欄位:

# Qwen3.8-27B:每 4 層 1 層全注意力、FP32 state,全寫在 config 裡
curl -s https://huggingface.co/Qwen/Qwen3.8-27B/raw/main/config.json \
  | grep -E "full_attention_interval|mamba_ssm_dtype|linear_num_value_heads"

# DeepSeek-V4-Flash-0731:top-512 選讀、window 128、出廠內建 YaRN ×16
# (裸名 DeepSeek-V4-Flash 是 preview,官方 card 自己說 0731 才是正式版)
curl -s https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-0731/raw/main/config.json \
  | grep -E 'index_topk|sliding_window|dspark_block_size|"factor"|yarn'

小結

  • **參數量猜不了 KV,架構才可以。**12B 在 8K 比 117B 貴,是因為七成成本卡在已經封頂的 sliding-window cache;context 一過約 16K 就反過來。
  • **「省 KV」可能省的是總量、選讀量,或只是 HBM 常駐,三者不是同一件事。**看到「省 30 倍」,先別問它有多快,先問它省的是哪一本帳。
  • 成本不會消失,只會搬家:搬成 FP32 固定 state、封頂 cache、indexer 算力,或 host RAM 與 PCIe 流量。模型沒有免費午餐,只有不同的帳單地址。

架構是天生體質,但同一副體質,行為還是後天練出來的:為什麼有的模型 thinking 恆開、有的預設關?

明天 Day 16〈元凶第一名:thinking mode 沒關〉把這件事變現成手感。Day 01 那晚是兩刀救回 40 秒,明天先把第一刀親手補量給你看,同一題,thinking 開與關,token 數與 decode 時間各差幾倍。

咱們明天見。


上一篇
Day 14 - 這台機器能同時服務幾個人?Little 定律與七檔容量速查表
下一篇
Day 16 - 45 秒裡有 27.5 秒在自言自語:thinking mode 什麼時候該關?
系列文
不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言